﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Windows.Forms;

namespace EbnfStudio
{

    #region Graph tree nodes

    /// <summary>
    /// Visitor pattern for traversing the graph tree
    /// </summary>
    interface IGraphVisitor
    {
        void Visit(GraphSyntaxElement ast);
        void Visit(GraphRuleElement ast);
        void Visit(GraphExpressionElement ast);
        void Visit(GraphTermElement ast);
        void Visit(GraphEmptyFactorElement ast);
        void Visit(GraphIdentifierElement ast);
        void Visit(GraphLiteralElement ast);
        void Visit(GraphGroupElement ast);
        void Visit(GraphOptionalElement ast);
        void Visit(GraphRepetitionElement ast);
    }


    // one item box. Hold width, height and method to visualize
    abstract class GraphElement
    {
        // Visitor pattern
        public abstract void Accept(IGraphVisitor visitor);

        // get item width
        public Size Size;
    }


    /// <summary>
    /// Contain the list of rules
    /// </summary>
    class GraphSyntaxElement : GraphElement
    {
        // visitor pattern
        public override void Accept(IGraphVisitor visitor) { visitor.Visit(this); }

        // the rules
        public List<GraphRuleElement> Rules;

        // constructor
        public GraphSyntaxElement(List<GraphRuleElement> rules)
        {
            Rules = rules;
        }
    }


    /// <summary>
    /// This class stores the sequence of Terms in the expression
    /// that are to be layed out side by side:
    /// Identifier = Expression
    /// </summary>
    class GraphRuleElement : GraphElement
    {
        // visitor pattern
        public override void Accept(IGraphVisitor visitor) { visitor.Visit(this); }

        // the identifier
        public GraphIdentifierElement Identifier;

        // hold the expression
        public GraphExpressionElement Expression;

        // constructor
        public GraphRuleElement(GraphIdentifierElement id, GraphExpressionElement expr)
        {
            Identifier = id; Expression = expr;
        }
    }


    /// <summary>
    /// Hold list of Terminal elements
    /// Term ->- Term -> Term
    /// </summary>
    class GraphExpressionElement : GraphElement
    {
        // visitor pattern
        public override void Accept(IGraphVisitor visitor) { visitor.Visit(this); }

        // the terminal elements
        public List<GraphTermElement> Terms;

        // constructor
        public GraphExpressionElement(List<GraphTermElement> terms)
        {
            Terms = terms;
        }
    }


    /// <summary>
    /// Hold information about one terminal
    /// Factors:
    /// +->- Factor ->-+
    /// +->- Factor ->-+
    /// </summary>
    class GraphTermElement : GraphElement
    {
        // visitor pattern
        public override void Accept(IGraphVisitor visitor) { visitor.Visit(this); }

        // list of Factors contained
        public List<GraphFactorElement> Factors;

        // constructor
        public GraphTermElement(List<GraphFactorElement> factors)
        {
            Factors = factors;
        }
    }


    /// <summary>
    /// Base interface for single factor element
    /// </summary>
    abstract class GraphFactorElement : GraphElement { }


    /// <summary>
    /// Display an empty production
    /// </summary>
    class GraphEmptyFactorElement : GraphFactorElement
    {
        // visitor pattern
        public override void Accept(IGraphVisitor visitor) { visitor.Visit(this); }
    }


    /// <summary>
    /// Identifier element
    /// </summary>
    class GraphIdentifierElement : GraphFactorElement
    {
        // visitor pattern
        public override void Accept(IGraphVisitor visitor) { visitor.Visit(this); }

        // the identifier token
        public Token Identifier;

        // constructor
        public GraphIdentifierElement(Token id)
        {
            Identifier = id;
        }
    }


    /// <summary>
    /// Identifier element
    /// </summary>
    class GraphLiteralElement : GraphFactorElement
    {
        // visitor pattern
        public override void Accept(IGraphVisitor visitor) { visitor.Visit(this); }

        // the identifier token
        public Token Literal;

        // constructor
        public GraphLiteralElement(Token literal)
        {
            Literal = literal;
        }
    }


    /// <summary>
    /// The group
    /// </summary>
    class GraphGroupElement : GraphFactorElement
    {
        // visitor pattern
        public override void Accept(IGraphVisitor visitor) { visitor.Visit(this); }

        // hold the expression
        public GraphExpressionElement Expression;

        // constructor
        public GraphGroupElement(GraphExpressionElement expr)
        {
            Expression = expr;
        }
    }


    /// <summary>
    /// The optional
    /// </summary>
    class GraphOptionalElement : GraphFactorElement
    {
        // visitor pattern
        public override void Accept(IGraphVisitor visitor) { visitor.Visit(this); }

        // hold the expression
        public GraphExpressionElement Expression;

        // constructor
        public GraphOptionalElement(GraphExpressionElement expr)
        {
            Expression = expr;
        }
    }


    /// <summary>
    /// The optional
    /// </summary>
    class GraphRepetitionElement : GraphFactorElement
    {
        // visitor pattern
        public override void Accept(IGraphVisitor visitor) { visitor.Visit(this); }

        // hold the expression
        public GraphExpressionElement Expression;

        // constructor
        public GraphRepetitionElement(GraphExpressionElement expr)
        {
            Expression = expr;
        }
    }

    #endregion


    /// <summary>
    /// Interface for graph tree rules. Mainly for infering
    /// various width and height of elements to properly construct the
    /// tree
    /// </summary>
    interface IGraphTreeConfig
    {
        /// <summary>
        /// Get size of the token
        /// </summary>
        Size GetSize(Token token);

        /// <summary>
        /// Get size of the given token class
        /// </summary>
        Size GetSize(TokenClass id);

        /// <summary>
        /// Get size of the Syntax tree
        /// </summary>
        /// <param name="count">Number of rules within syntax tree</param>
        /// <param name="size">total size of all rules combined</param>
        /// <param name="max">maximum width and height of the contained rules</param>
        Size GetSyntaxSize(int count, Size size, Size max);

        /// <summary>
        /// Get rule size
        /// </summary>
        /// <param name="maxIdSize">the maximum id size</param>
        /// <param name="exprSize">size of the contained expression</param>
        Size GetRuleSize(Size maxIdSize, Size exprSize);

        /// <summary>
        /// get size of the expression
        /// </summary>
        /// <param name="count">number terminals contained</param>
        /// <param name="size">Total size of the terminals</param>
        /// <param name="max">Maximum terminal size</param>
        /// <returns>Size of the expression block</returns>
        Size GetExprSize(int count, Size size, Size max);

        /// <summary>
        /// Get size for the Term.
        /// </summary>
        /// <param name="count">Number of factors inside the terminal</param>
        /// <param name="size">Size of all factors combined</param>
        /// <param name="max">Max width and height of all terminals contained</param>
        Size GetTermSize(int count, Size size, Size max);

        /// <summary>
        /// Get size of the group
        /// </summary>
        /// <param name="size">Size of the containes expression</param>
        Size GetGroupSize(Size size);

        /// <summary>
        /// Get size of the optional group
        /// </summary>
        /// <param name="size">size of the expression contained</param>
        Size GetOptionalSize(Size size);

        /// <summary>
        /// get size of the repetition group
        /// </summary>
        /// <param name="size">size of the expression contained</param>
        Size GetRepetitionSize(Size size);

    }


    /// <summary>
    /// Turn ast tree into a bitmap
    /// </summary>
    class GraphTree : IAstVisitor
    {

        /// <summary>
        /// Graph tree rules
        /// </summary>
        public IGraphTreeConfig Config;

        /// <summary>
        /// The tree
        /// </summary>
        public GraphSyntaxElement SyntaxTree; 

        /// <summary>
        /// Last created item
        /// </summary>
        private GraphElement Element;


        /// <summary>
        /// constructor
        /// </summary>
        /// <param name="rules"></param>
        public GraphTree(IGraphTreeConfig config)
        {
            Config = config;
        }


        /// <summary>
        /// Syntax = { Rule } .
        /// </summary>
        public virtual void Visit(AstSyntax ast)
        {
            var rules = new List<GraphRuleElement>();

            var cnt = ast.Rules.Count;
            for (int i = 0; i < cnt; i++)
            {
                ast.Rules[i].Accept(this);
                rules.Add((GraphRuleElement)Element);
            }
            SyntaxTree = new GraphSyntaxElement(rules);
            Element = null;

            // calc
            if (rules.Count == 0)
            {
                SyntaxTree.Size = new Size(0, 0);
                return;
            }

            // find the longest identifier
            var maxIdSize = new Size(
                rules.Max(w => Config.GetSize(w.Identifier.Identifier).Width),
                rules.Max(h => Config.GetSize(h.Identifier.Identifier).Height)
            );
            var totalIdSize = new Size(
                rules.Sum(w => Config.GetSize(w.Identifier.Identifier).Width),
                rules.Sum(h => Config.GetSize(h.Identifier.Identifier).Height)
            );
            // calculate rule sizes
            foreach (var rule in rules)
            {
                rule.Size = Config.GetRuleSize(maxIdSize, rule.Expression.Size);
                rule.Identifier.Size = maxIdSize;
            }

            // calculate the size of enite syntax tree
            // calc
            SyntaxTree.Size = Config.GetSyntaxSize(
                rules.Count,
                new Size(
                    rules.Sum(w => w.Size.Width),
                    rules.Sum(h => h.Size.Height)
                ),
                new Size(
                    rules.Max(w => w.Size.Width),
                    rules.Max(h => h.Size.Height)
                )
            );

        }


        /// <summary>
        /// Rule = Identifier "=" Expression ( "." | ";" ) .
        /// </summary>
        public void Visit(AstRule ast)
        {
            // the expression
            ast.Expression.Accept(this);
            var rule = new GraphRuleElement(
                new GraphIdentifierElement(ast.Identifier),
                (GraphExpressionElement)Element
            );
            Element = rule;

            // calculate size in the Visit(AstSyntax ast)
            // because other Identifier sizes are unknown here
        }


        /// <summary>
        /// Expression = Term { Term } .
        /// </summary>
        public void Visit(AstExpression ast)
        {
            var terms = new List<GraphTermElement>();
            var cnt = ast.Terms.Count;
            for (int i = 0; i < cnt; i++)
            {
                ast.Terms[i].Accept(this);
                terms.Add((GraphTermElement)Element);
            }
            var expr = new GraphExpressionElement(terms);
            Element = expr;

            // no items
            if (terms.Count == 0)
            {
                expr.Size = new Size(0, 0);
                return;
            }

            // calc
            expr.Size = Config.GetExprSize(
                terms.Count,
                new Size(
                    terms.Sum(w => w.Size.Width),
                    terms.Sum(h => h.Size.Height)
                ),
                new Size(
                    terms.Max(w => w.Size.Width),
                    terms.Max(h => h.Size.Height)
                )
            );
        }


        /// <summary>
        /// Term = Factor { "|" Factor } .
        /// </summary>
        public void Visit(AstTerm ast)
        {
            var factors = new List<GraphFactorElement>();
            var cnt = ast.Factors.Count;
            for (int i = 0; i < cnt; i++)
            {
                ast.Factors[i].Accept(this);
                factors.Add((GraphFactorElement)Element);
            }
            var term = new GraphTermElement(factors);
            Element = term;

            // calc
            term.Size = Config.GetTermSize (
                factors.Count,
                new Size(
                    factors.Sum(w => w.Size.Width),
                    factors.Sum(h => h.Size.Height)
                ),
                new Size(
                    factors.Max(w => w.Size.Width),
                    factors.Max(h => h.Size.Height)
                )
            );
        }


        /// <summary>
        /// ε (* empty production *)
        /// </summary>
        public void Visit(AstEmptyFactor ast)
        {
            var el = new GraphEmptyFactorElement();
            Element = el;

            // calc
            el.Size = Config.GetSize(TokenClass.Empty);
        }


        /// <summary>
        /// Identifier
        /// </summary>
        public void Visit(AstIdentifier ast)
        {
            var el = new GraphIdentifierElement(ast.Identifier);
            Element = el;

            // calc
            el.Size = Config.GetSize(ast.Identifier);
        }


        /// <summary>
        /// Literal
        /// </summary>
        public void Visit(AstLiteral ast)
        {
            var el = new GraphLiteralElement(ast.Literal);
            Element = el;

            // calc
            el.Size = Config.GetSize(ast.Literal);
        }


        /// <summary>
        /// Group = "(" Expression ")" .
        /// </summary>
        public void Visit(AstGroup ast)
        {
            ast.Expression.Accept(this);
            var expr = (GraphExpressionElement)Element;
            var group = new GraphGroupElement(expr);
            Element = group;

            // calc
            group.Size = Config.GetGroupSize(expr.Size);
        }


        /// <summary>
        /// Optional = "[" Expression "]" .
        /// </summary>
        public void Visit(AstOptional ast)
        {
            ast.Expression.Accept(this);
            var expr = (GraphExpressionElement)Element;
            var optional = new GraphOptionalElement(expr);
            Element = optional;

            // calc
            optional.Size = Config.GetOptionalSize(expr.Size);
        }


        /// <summary>
        /// Repetition = "{" Expression "}" .
        /// </summary>
        public void Visit(AstRepetition ast)
        {
            ast.Expression.Accept(this);
            var expr = (GraphExpressionElement)Element;
            var repetition = new GraphRepetitionElement(expr);
            Element = repetition;

            // calc
            repetition.Size = Config.GetRepetitionSize(expr.Size);
        }

    }
}
